/*
 * Copyright (C) 2004 Derek James and Philip Tucker
 * 
 * This file is part of JGAP.
 * 
 * JGAP is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 * 
 * JGAP is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with
 * JGAP; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA 02111-1307 USA
 * 
 * Created on Feb 3, 2003 by Philip Tucker
 */
package org.jgap;

import java.util.List;

import org.jgap.impl.CloneReproductionOperator;

/**
 * Abstract crossover reporduction operator handles iteration over population to
 * determine pairs of parent chromosomes. Subclass determines specific crossover
 * logic by implementing <code>reproduce(Configuration config, 
 * Chromosome dominantChrom, Chromosome recessiveChrom)</code>.
 * 
 * @author Philip Tucker
 */
public abstract class CrossoverReproductionOperator extends
    ReproductionOperator {

  // TODO - redo such that pairing logic and crossover logic can be swapped
  // independantly; i.e., use aggregation not inheritance

  /**
   * @param config
   * @param dominantChrom
   * @param recessiveChrom
   * @return offspring of <code>dominantChrom</code> and
   * <code>recessiveChrom</code>
   */
  protected abstract ChromosomeMaterial reproduce(Configuration config,
      Chromosome dominantChrom, Chromosome recessiveChrom);

  /**
   * Adds new children of <code>parents</code> to <code>offspring</code>.
   * Chromosomes "mate" only within their species, and the number of offspring
   * for each specie is determined by the size and average fitness of that
   * specie. Species containing only 1 chromosome generate offspring via
   * cloning.
   * @param config
   * @param parentChroms <code>List</code> contains <code>Chromosome</code>
   * objects
   * @param offspring <code>List</code> contains <code>ChromosomeMaterial</code>
   * objects, offspring of parents; total number of chromosomes in parents and
   * offspring should equal config.getPopulationSize()
   * @see ReproductionOperator#reproduce(Configuration, List, int, List)
   */
  final protected void reproduce(final Configuration config,
      final List parentChroms, int numOffspring, List offspring) {
    if (parentChroms.size() < 1)
      throw new IllegalArgumentException("crossover requires at least 1 parent");

    // if only one parent, clone instead of crossover
    if (parentChroms.size() < 2)
      CloneReproductionOperator
          .reproduce(parentChroms, numOffspring, offspring);
    else {
      int targetSize = offspring.size() + numOffspring;

      while (offspring.size() < targetSize) {
        // select random "mother" and "father"
        int motherIdx =
            config.getRandomGenerator().nextInt(parentChroms.size());
        int fatherIdx = motherIdx;
        while (fatherIdx == motherIdx)
          fatherIdx = config.getRandomGenerator().nextInt(parentChroms.size());

        // determine dominance/recessiveness
        Chromosome dominant = null;
        Chromosome recessive = null;
        Chromosome mother = (Chromosome) parentChroms.get(motherIdx);
        Chromosome father = (Chromosome) parentChroms.get(fatherIdx);
        // TODO - use speciated fitness here?
        if (mother.getFitnessValue() > father.getFitnessValue()) {
          dominant = mother;
          recessive = father;
        } else {
          recessive = mother;
          dominant = father;
        }
        ChromosomeMaterial child = reproduce(config, dominant, recessive);
        offspring.add(child);
      }
    }
  }

}
